package org.groovymud.engine;

/* Copyright 2008 Matthew Corby-Eaglen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */

import groovy.util.ResourceException;
import groovy.util.ScriptException;
import net.wimpi.telnetd.TelnetD;
import org.apache.log4j.Logger;
import org.groovymud.engine.event.HeartBeatListener;
import org.groovymud.object.MudObject;
import org.groovymud.object.alive.Player;
import org.groovymud.object.registry.MudObjectAttendant;
import org.groovymud.object.registry.Registry;
import org.groovymud.object.room.Room;
import org.groovymud.shell.Shell;
import org.groovymud.shell.security.MudLoginModule;
import org.groovymud.shell.telnetd.ShellBridge;
import org.groovymud.utils.CountingMap;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;

/**
 * The mud engine is responsible for starting up the mud, shutting it down,
 * distributing heartbeats, resetting rooms and registering player states.
 *
 * @author matt
 */
public abstract class JMudEngine implements Runnable {

    public static int heartBeatsUntilAutosave = 60;
    public static int heartBeatsUntilReset = 1200;

    public static String netDeadStatus = "net dead";

    protected static int mudHeartbeatLength = 1000;

    private static TelnetD telnetDaemon;

    private final Registry objectRegistry;

    private volatile boolean running;

    private final static Logger logger = Logger.getLogger(JMudEngine.class);

    private static AbstractApplicationContext context;

    private final MudObjectAttendant objectAttendant;

    private final CountingMap<Player> netDeadPlayers;

    private static boolean shutdownRequested = false;

    private final ExecutorService executor;

    public JMudEngine(Registry registry, MudObjectAttendant attendant, CountingMap<Player> netDeadMap, ExecutorService exec, TelnetD daemon) {
        running = true;
        this.objectRegistry = registry;
        this.objectAttendant = attendant;
        this.netDeadPlayers = netDeadMap;
        this.executor = exec;
        telnetDaemon = daemon;
    }

    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException, ResourceException, ScriptException {
        start();
    }

    public static JMudEngine start() {
        System.out.println("workspace.loc:" + System.getProperty("workspace.loc"));
        System.out.println("lib:" + System.getProperty("lib"));
        System.out.println("mudspace:" + System.getProperty("mudspace"));

        createSpringContext();
        ShellBridge.setApplicationContext(context);
        MudLoginModule.setApplicationContext(context);
        final JMudEngine engine = context.getBean(JMudEngine.class);

        engine.getExecutor().execute(engine);

        telnetDaemon.start();
        return engine;
    }

    public static void createSpringContext() {
        context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml", "sshdContext.xml", "telnetdContext.xml", "/commandAliasContext.xml", "loginModuleContext.xml"}, JMudEngine.class);
        context.refresh();
        context.registerShutdownHook();
    }

    public void run() {
        logger.info("GroovyMud up and running");
        int saveTime = 0;
        int resetTime = 0;
        while (isRunning()) {
            try {
                doHeartBeat();
                handleTheNetDead();
                checkPlayerHandles();

                if (saveTime++ == heartBeatsUntilAutosave) {
                    savePlayers();
                    saveTime = 0;
                }
                if (resetTime++ == heartBeatsUntilReset) {
                    resetRooms();
                    resetTime = 0;
                }
                if (isShutdownRequested()) {
                    createShutdownBehaviour().handleShutdown();
                }
                Thread.sleep(mudHeartbeatLength);
            } catch (InterruptedException e) {
                logger.error(e, e);
            }
        }

    }

    protected void resetRooms() {
        logger.info("Resetting rooms");
        Iterator<MudObject> i = getObjectRegistry().getMudObjects().iterator();
        while (i.hasNext()) {
            MudObject o = i.next();

            if (o instanceof Room) {
                o.initialise();
            }
            Thread.yield();
        }

    }

    protected void savePlayers() {

        Iterator<Shell> i = getObjectRegistry().getActivePlayerHandles().iterator();
        if (i.hasNext()) {
            logger.info("auto saving..");
        }
        while (i.hasNext()) {
            Shell o = i.next();
            Player p = getObjectRegistry().getPlayerByHandle(o);
            if (getObjectRegistry().getMudObject(p.getName()) == null) {
                continue;
            }
            getObjectAttendant().savePlayerData(p);
        }
    }

    protected void checkPlayerHandles() {
        CountingMap<Player> netDead = getNetDeadPlayers();
        Set<Player> totallyDead = netDead.getKeysAbove(30);

        for (Player player : totallyDead) {
            player.removeStatus(netDeadStatus);
            getObjectRegistry().dest(player, false);
            getNetDeadPlayers().remove(player);
        }

    }

    protected void doHeartBeat() {
        Set<HeartBeatListener> objects = getObjectRegistry().getHeartBeatListeners();
        Iterator<HeartBeatListener> i = objects.iterator();
        while (i.hasNext()) {
            HeartBeatListener o = i.next();

            if (o instanceof HeartBeatListener) {
                try {
                    o.heartBeat();
                } catch (Exception e) {
                    logger.error("Heartbeat on object " + o + " threw exception.", e);
                }
            }
            Thread.yield();
        }

    }

    protected void handleTheNetDead() {

        Iterator<Shell> x = new HashSet<Shell>(getObjectRegistry().getActivePlayerHandles()).iterator();
        CountingMap<Player> netDead = getNetDeadPlayers();
        while (x.hasNext()) {
            Shell shell = x.next();
            if (!shell.isConnectionActive()) {
                Player player = getObjectRegistry().getPlayerByHandle(shell);
                if (!netDead.containsKey(player)) {
                    logger.info(player.getName() + " is net dead..");
                    player.addStatus(netDeadStatus);
                }
                netDead.increment(player);
            }
        }
    }

    public boolean isRunning() {
        return running;
    }

    public void setRunning(boolean runningArg) {
        this.running = runningArg;
    }

    public static void requestShutDown(Player requester) {
        logger.info("shutdown requested!!!");

        shutdownRequested = true;

    }

    public static AbstractApplicationContext getContext() {
        return context;
    }

    public Registry getObjectRegistry() {
        return objectRegistry;
    }

    public MudObjectAttendant getObjectAttendant() {
        return objectAttendant;
    }

    public TelnetD getTelnetDaemon() {
        return telnetDaemon;
    }

    public void setTelnetDaemon(TelnetD telnetDeamon) {
        JMudEngine.telnetDaemon = telnetDeamon;
    }

    public CountingMap<Player> getNetDeadPlayers() {
        return netDeadPlayers;
    }

    public void setShutdownRequested(boolean shutdownRequest) {
        this.shutdownRequested = shutdownRequest;
    }

    public boolean isShutdownRequested() {
        return shutdownRequested;
    }

    public void shutdownNow() {
        logger.info("shutdownNow called");
        destAllPlayers();
        getTelnetDaemon().stop();
        this.setRunning(false);
        logger.info("shudownNow complete");
//        System.exit(0);
    }

    protected void destAllPlayers() {
        Iterator<Shell> x = new HashSet<Shell>(getObjectRegistry().getActivePlayerHandles()).iterator();
        while (x.hasNext()) {
            Shell shell = x.next();
            if (shell.isConnectionActive()) {
                Player p = getObjectRegistry().getPlayerByHandle(shell);
                getObjectRegistry().dest(p, false);
            }
        }
    }

    public abstract ShutdownBehaviour createShutdownBehaviour();

    public ExecutorService getExecutor() {
        return executor;
    }

    public int getHeartBeatsUntilAutosave() {
        return heartBeatsUntilAutosave;
    }

    public static void setHeartBeatsUntilAutosave(int heartBeats) {
        JMudEngine.heartBeatsUntilAutosave = heartBeats;
    }

    public int getHeartBeatsUntilReset() {
        return heartBeatsUntilReset;
    }

    public void setHeartBeatsUntilReset(int heartBeats) {
        JMudEngine.heartBeatsUntilReset = heartBeats;
    }

    public String getNetDeadStatus() {
        return netDeadStatus;
    }

    public void setNetDeadStatus(String status) {
        JMudEngine.netDeadStatus = status;
    }

    public int getMudHeartbeatLength() {
        return mudHeartbeatLength;
    }

    public void setMudHeartbeatLength(int length) {
        JMudEngine.mudHeartbeatLength = length;
    }

}
